home *** CD-ROM | disk | FTP | other *** search
/ NeXT Education Software Sampler 1992 Fall / NeXT Education Software Sampler 1992 Fall.iso / Programming / Source / HippoDraw / HippoDrawSrc1.1 / Hippo.subproj / GraphicView.m < prev    next >
Encoding:
Text File  |  1992-04-25  |  68.4 KB  |  2,640 lines

  1. #import "GraphicView.h"
  2. #import "Rectangle.h"
  3. #import "Group.h"
  4. #import "Image.h"
  5. #import "draw.h"
  6. #import <appkit/NXCursor.h>
  7. #import <appkit/NXColorWell.h>
  8. #import <appkit/Matrix.h>
  9. #import <appkit/Panel.h>
  10. #import <appkit/Pasteboard.h>
  11. #import <appkit/Text.h>
  12. #import <appkit/defaults.h>
  13. #import <appkit/nextstd.h>
  14. #import <appkit/tiff.h>
  15. #import <appkit/timer.h>
  16. #import <dpsclient/wraps.h>
  17. #import <objc/List.h>
  18. #import <string.h>
  19. #import <math.h>
  20. #import <zone.h>
  21. #import <mach.h>
  22. #import <sys/param.h>
  23.  
  24. /* This file is best read in a window as wide as this comment (so that this comment fits on one line). */
  25.  
  26. #define Notify(title, msg) NXRunAlertPanel(title, msg, NULL, NULL, NULL)
  27.  
  28. const char *DrawPboardType = "Draw Graphic List Type";
  29.  
  30. BOOL InMsgPrint = NO;        /* whether we are in msgPrint: */
  31.  
  32. #define LEFTARROW    172
  33. #define RIGHTARROW    174
  34. #define UPARROW        173
  35. #define DOWNARROW    175
  36.  
  37. @implementation GraphicView : View
  38. /*
  39.  * The GraphicView class is the core of a DrawDocument.
  40.  *
  41.  * It overrides the View methods related to drawing and event handling
  42.  * and allows manipulation of Graphic objects.
  43.  *
  44.  * The user is allowed to select objects, move them around, group and
  45.  * ungroup them, change their font, and cut and paste them to the pasteboard.
  46.  * It understands multiple formats including PostScript and TIFF as well as
  47.  * its own internal format.  The GraphicView can also import PostScript and
  48.  * TIFF documents and manipulate them as Graphic objects.
  49.  *
  50.  * This is a very skeleton implementation and is intended purely for
  51.  * example purposes.  It should be very easy to add new Graphic objects
  52.  * merely by subclassing the Graphic class.  No code need be added to the
  53.  * GraphicView class when a new Graphic subclass is added.
  54.  *
  55.  * Moving is accomplished using a selection cache which is shared among
  56.  * all instances of GraphicView in the application.  The objects in the
  57.  * selection are drawn using opaque ink on a transparent background so
  58.  * that when they are moved around, the user can see through them to the
  59.  * objects that are not being moved.
  60.  *
  61.  * All of the drawing is done in an off-screen window which is merely
  62.  * composited back to the screen.  This makes for very fast redraw of
  63.  * areas obscured either by the selection moving or the user's scrolling.
  64.  *
  65.  * The glist instance variable is just an ordered list of all the Graphics
  66.  * in the GraphicView.  The slist is an ordered list of the Graphic objects
  67.  * in the selection.  cacheWindow is the off-screen window into which the
  68.  * objects are drawn.  Flags: dirty indicates that the user has modified
  69.  * the collection of objects since the bit was last cleared; grid is the
  70.  * distance between pixels in the grid imposed on drawing; cacheing is used
  71.  * so that drawSelf:: knows when to composite from the off-screen cache and
  72.  * when to draw into the off-screen cache; groupInSlist is used to keep
  73.  * track of whether a Group Graphic is in the slist so that it knows when
  74.  * to highlight the Ungroup entry in the menu.
  75.  *
  76.  * This class should be able to be used outside the context of this
  77.  * application since it takes great pains not to depend on any other objects
  78.  * in the application.
  79.  */
  80.  
  81. /*
  82.  * Of course, one should NEVER use global variables in an application, but
  83.  * the following is necessary.  DrawStatus is
  84.  * analogous to the Application Kit's NXDrawingStatus and reflects whether
  85.  * we are in some modal loop.  By definition, that modal loop is "atomic"
  86.  * since we own the mouse during its duration (of course, all bets are off
  87.  * if we have multiple mice!).
  88.  */
  89.  
  90. DrawStatusType DrawStatus = Normal;  /* global state reflecting what we
  91.                     are currently doing (resizing, etc.) */
  92.  
  93. const float DEFAULT_GRID_GRAY = 0.8333;
  94.  
  95. static id currentGraphic = nil;    /* won't be used if NXApp knows how
  96.                    to keep track of the currentGraphic */
  97.  
  98. static float KeyMotionDeltaDefault = 0.0;
  99.  
  100. /* Private C functions needed to implement methods in this class. */
  101.  
  102. static id createCache(NXSize *size, NXZone *zone)
  103. /*
  104.  * Creates an appropriately size off-screen window to cache bits in.
  105.  */
  106. {
  107.     NXRect cacheRect;
  108.  
  109.     cacheRect.origin.x = 0.0;
  110.     cacheRect.origin.y = 0.0;
  111.     cacheRect.size = *size;
  112.  
  113.     return [[[Window allocFromZone:zone] initContent:&cacheRect
  114.                            style:NX_PLAINSTYLE
  115.                          backing:NX_RETAINED
  116.                       buttonMask:0
  117.                            defer:NO] reenableDisplay];
  118. }
  119.  
  120. /* Code-cleaning macros */
  121.  
  122. #define stopTimer(timer) if (timer) { \
  123.     NXEndTimer(timer); \
  124.     timer = NULL; \
  125. }
  126.  
  127. #define startTimer(timer) if (!timer) timer = NXBeginTimer(NULL, 0.1, 0.1);
  128.  
  129. #define GRID (gvFlags.gridDisabled ? 1.0 : (NXCoord)gvFlags.grid)
  130.  
  131. #define grid(point) \
  132.     (point).x = floor(((point).x / GRID) + 0.5) * GRID; \
  133.     (point).y = floor(((point).y / GRID) + 0.5) * GRID;
  134.  
  135. #define DIRTY(condition) \
  136.     if (condition && !gvFlags.dirty) { \
  137.     gvFlags.dirty = YES; \
  138.     [window setDocEdited:YES]; \
  139.     }    
  140.  
  141. static void getRegion(NXRect *region, const NXPoint *p1, const NXPoint *p2)
  142. /*
  143.  * Returns the rectangle which has p1 and p2 as its corners.
  144.  */
  145. {
  146.     region->size.width = p1->x - p2->x;
  147.     region->size.height = p1->y - p2->y;
  148.     if (region->size.width < 0.0) {
  149.     region->origin.x = p2->x + region->size.width;
  150.     region->size.width = ABS(region->size.width);
  151.     } else {
  152.     region->origin.x = p2->x;
  153.     }
  154.     if (region->size.height < 0.0) {
  155.     region->origin.y = p2->y + region->size.height;
  156.     region->size.height = ABS(region->size.height);
  157.     } else {
  158.     region->origin.y = p2->y;
  159.     }
  160. }
  161.  
  162. static BOOL checkForGroup(id list)
  163. /*
  164.  * Looks through the given list searching for objects of the Group class.
  165.  * We use this to keep the gvFlags.groupInSlist flag up to date when things
  166.  * are removed from the slist (the list of selected objects).  That way
  167.  * we can efficiently keep the Ungroup menu item up to date.
  168.  */
  169. {
  170.     int i = [list count];
  171.     while (i--) if ([[list objectAt:i] isKindOf:[Group class]]) return YES;
  172.     return NO;
  173. }
  174.  
  175. /* Hack to support growable Text objects. */
  176.  
  177. static id createEditView(GraphicView *self)
  178. /*
  179.  * editView is essentially a dumb, FLIPPED (with extra emphasis on the
  180.  * flipped) subview of our GraphicView which completely covers it and
  181.  * which automatically sizes itself to always completely cover the
  182.  * GraphicView.  It is necessary since growable Text objects only work
  183.  * when they are subviews of a flipped view.
  184.  *
  185.  * See TextGraphic for more details about why we need editView
  186.  * (it is purely a workaround for a limitation of the Text object).
  187.  */
  188. {
  189.     id view;
  190.  
  191.     [self setAutoresizeSubviews:YES];
  192.     view = [[View allocFromZone:[self zone]] initFrame:&(self->frame)];
  193.     [view setFlipped:YES];
  194.     [view setAutosizing:NX_WIDTHSIZABLE|NX_HEIGHTSIZABLE];
  195.     [self addSubview:view];
  196.  
  197.     return view;
  198. }
  199.  
  200. /* Routines to check the types in the Pasteboard */
  201.  
  202. static BOOL matchPasteType(const NXAtom *types, NXAtom type)
  203. {
  204.     if (types) while (*types) if (*types++ == type) return YES;
  205.     return NO;
  206. }
  207.  
  208. static NXAtom drawPasteType(const NXAtom *types)
  209. /*
  210.  * Returns the pasteboard type in the passed list of types which is preferred
  211.  * by the Draw program for pasting.  The Draw program prefers its own type
  212.  * of course, then it prefers PostScript over TIFF.
  213.  */
  214. {
  215.     if (matchPasteType(types, DrawPboardType)) return DrawPboardType;
  216.     if (matchPasteType(types, NXPostScriptPboardType)) return NXPostScriptPboardType;
  217.     if (matchPasteType(types, NXTIFFPboardType)) return NXTIFFPboardType;
  218.     return NULL;
  219. }
  220.  
  221. /* Factory methods. */
  222.  
  223. + initialize
  224. /*
  225.  * We up the version of the class so that we can read old .draw files.
  226.  * See the read: method for how we use the version.
  227.  */
  228. {
  229.     [self setVersion:1];
  230.     DrawPboardType = NXUniqueStringNoCopy(DrawPboardType);
  231.     return self;
  232. }
  233.  
  234. /* Lazy Pasteboard evaluation handler */
  235.  
  236. /*
  237.  * IMPORTANT: The pasteboard:provideData: method is a factory method since the
  238.  * factory object is persistent and there is no guarantee that the INSTANCE of
  239.  * GraphicView that put the Draw format into the Pasteboard will be around
  240.  * to lazily put PostScript or TIFF in there, so we keep one around (actually
  241.  * we only create it when we need it) to do the conversion (scrapper).
  242.  *
  243.  * If you find this part of the code confusing, then you need not even
  244.  * use the provideData: mechanism--simply put the data for all the different
  245.  * types your program knows how to put in the Pasteboard in at the time
  246.  * that you declareTypes:.
  247.  */
  248.  
  249. + convert:(NXTypedStream *)ts to:(const char *)type using:(SEL)writer toPasteboard:pb
  250. /*
  251.  * Converts the data in the Pasteboard from Draw internal format to
  252.  * either PostScript or TIFF using the writeTIFFToStream: and writePSToStream:
  253.  * methods.  It sends these messages to the scrapper (a GraphicView cached
  254.  * to perform this very function).  Note that the scrapper view is put in
  255.  * a window, but that window is off-screen, has no backing store, and no
  256.  * title (and is thus very cheap).
  257.  */
  258. {
  259.     id w;
  260.     char *data;
  261.     NXZone *zone;
  262.     NXStream *stream;
  263.     int length, maxlen;
  264.     GraphicView *scrapper;
  265.     const NXRect scrapperFrame = {{0.0, 0.0}, {11.0*72.0, 14.0*72.0}};
  266.  
  267.     if (!ts) return self;
  268.  
  269.     zone = NXCreateZone(vm_page_size, vm_page_size, NO);
  270.     NXNameZone(zone, "Scrapper");
  271.     scrapper = [[GraphicView allocFromZone:zone] initFrame:&scrapperFrame];
  272.     w = [[Window allocFromZone:zone] initContent:&scrapperFrame
  273.                        style:NX_PLAINSTYLE
  274.                      backing:NX_NONRETAINED
  275.                       buttonMask:0
  276.                        defer:NO];
  277.     [w reenableDisplay];
  278.     [w setContentView:scrapper];
  279.  
  280.     stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
  281.     NXSetTypedStreamZone(ts, zone);
  282.     scrapper->glist = NXReadObject(ts);
  283.     [scrapper perform:writer with:(id)stream];
  284.     NXGetMemoryBuffer(stream, &data, &length, &maxlen);
  285.     [pb writeType:type data:data length:length];
  286.     NXCloseMemory(stream, NX_FREEBUFFER);
  287.     [w free];
  288.     NXDestroyZone(zone);
  289.  
  290.     return self;
  291. }
  292.  
  293.  
  294. + pasteboard:sender provideData:(const char *)type
  295. /*
  296.  * Called by the Pasteboard whenever PostScript or TIFF data is requested
  297.  * from the Pasteboard by some other application.  The current contents of
  298.  * the Pasteboard (which is in the internal format) is taken out and loaded
  299.  * into a stream, then convert:to:using:toPasteboard: is called.
  300.  */
  301. {
  302.     char *data;
  303.     int length;
  304.     NXStream *stream;
  305.     NXTypedStream *ts;
  306.  
  307.     if (type) {
  308.     [sender readType:DrawPboardType data:&data length:&length];
  309.     stream = NXOpenMemory(data, length, NX_READONLY);
  310.     if (stream) {
  311.         ts = NXOpenTypedStream(stream, NX_READONLY);
  312.         if (ts) {
  313.         if (type == NXPostScriptPboardType) {
  314.             [self convert:ts to:NXPostScriptPboardType using:@selector(writePSToStream:) toPasteboard:sender];
  315.         } else if (type == NXTIFFPboardType) {
  316.             [self convert:ts to:NXTIFFPboardType using:@selector(writeTIFFToStream:) toPasteboard:sender];
  317.         }
  318.         NXCloseTypedStream(ts);
  319.         }
  320.         NXCloseMemory(stream, NX_FREEBUFFER);
  321.     }
  322.     }
  323.  
  324.     return self;
  325. }
  326.  
  327. /* Creation methods. */
  328.  
  329. static void initClassVars()
  330. /*
  331.  * Sets up any default values and cursors.
  332.  */
  333. {
  334.     static BOOL registered = NO;
  335.     const char *validSendTypes[4];
  336.     const char *validReturnTypes[4];
  337.  
  338.     if (!KeyMotionDeltaDefault) {
  339.     const char *value = NXGetDefaultValue([NXApp appName], "KeyMotionDelta");
  340.     if (value) KeyMotionDeltaDefault = atof(value);
  341.     KeyMotionDeltaDefault = MAX(KeyMotionDeltaDefault, 1.0);
  342.     }
  343.     if (!registered) {
  344.     registered = YES;
  345.     validSendTypes[0] = NXPostScriptPboardType;
  346.     validSendTypes[1] = NXTIFFPboardType;
  347.     validSendTypes[2] = DrawPboardType;
  348.     validSendTypes[3] = NULL;
  349.     validReturnTypes[0] = NXPostScriptPboardType;
  350.     validReturnTypes[1] = NXTIFFPboardType;
  351.     validReturnTypes[2] = DrawPboardType;
  352.     validReturnTypes[3] = NULL;
  353.     [NXApp registerServicesMenuSendTypes:validSendTypes andReturnTypes:validReturnTypes];
  354.     }
  355. }
  356.  
  357. - initFrame:(const NXRect *)frameRect
  358. /*
  359.  * Initializes the view's instance variables.
  360.  * This view is considered important enough to allocate it a gstate.
  361.  */
  362. {
  363.     [super initFrame:frameRect];
  364.     glist = [[List allocFromZone:[self zone]] init];
  365.     slist = [[List allocFromZone:[self zone]] init];
  366.     cacheWindow = createCache(&bounds.size, [self zone]);
  367.     gvFlags.grid = 1;
  368.     [self allocateGState];
  369.     gridGray = DEFAULT_GRID_GRAY;
  370.     PSInit();
  371.     currentGraphic = [Rectangle class];          /* default graphic */
  372.     currentGraphic = [self currentGraphic];   /* trick to allow NXApp to control currentGraphic */
  373.     editView = createEditView(self);
  374.     initClassVars();
  375.     return self;
  376. }
  377.  
  378. /* Free method */
  379.  
  380. - free
  381. {
  382.     if (gupCoords) {
  383.     NX_FREE(gupCoords);
  384.     NX_FREE(gupOps);
  385.     NX_FREE(gupBBox);
  386.     }
  387.     [glist freeObjects];
  388.     [glist free];
  389.     [slist free];
  390.     [cacheWindow free];
  391.     if (![editView superview]) [editView free];
  392.     return [super free];
  393. }
  394.  
  395. /* Services menu methods */
  396.  
  397. - validRequestorForSendType:(NXAtom)sendType andReturnType:(NXAtom)returnType
  398. /*
  399.  * We are a valid requestor whenever any of the send or
  400.  * return types is PostScript, TIFF, or Draw.
  401.  * Note that we cache around the NXAtom for each
  402.  * type since it is important this this method be fast
  403.  * (it gets called relatively often).
  404.  */
  405. {
  406.  
  407.     if ((!sendType || !*sendType ||
  408.     ((sendType == NXPostScriptPboardType ||
  409.       sendType == NXTIFFPboardType ||
  410.       sendType == DrawPboardType) && [slist count])) &&
  411.     (!returnType || !*returnType ||
  412.       returnType == NXPostScriptPboardType ||
  413.       returnType == NXTIFFPboardType ||
  414.       returnType == DrawPboardType)) {
  415.     return self;
  416.     }
  417.  
  418.     return [super validRequestorForSendType:sendType andReturnType:returnType];
  419. }
  420.  
  421. - (BOOL)writeSelectionToPasteboard:pboard types:(NXAtom *)types
  422. /*
  423.  * If one of the requested types is one of the ones we handle,
  424.  * then we put our selection in the Pasteboard.
  425.  */
  426. {
  427.     while (types && *types) {
  428.     if (*types == NXPostScriptPboardType || *types == NXTIFFPboardType || *types == DrawPboardType) break;
  429.     types++;
  430.     }
  431.  
  432.     if (types && *types && [self copyToPasteboard:pboard types:types]) {
  433.     serviceActsOnSelection = YES;
  434.     return YES;
  435.     } else {
  436.     return NO;
  437.     }
  438. }
  439.  
  440. - readSelectionFromPasteboard:pboard
  441. /*
  442.  * When a result comes back from the Services menu request,
  443.  * we replace the selection with the return value.
  444.  * If the user really wants the return value in addition to
  445.  * the current selection, she can simply copy, then paste
  446.  * twice to get two copies, then choose the Services menu item.
  447.  */
  448. {
  449.     if (serviceActsOnSelection) [self delete:self];
  450.     serviceActsOnSelection = NO;
  451.     [[self pasteFromPasteboard:pboard] free];
  452.     return self;
  453. }
  454.  
  455. /* Private selection management methods. */
  456.  
  457. - selectionCache
  458. /*
  459.  * Shares an off-screen window used to draw the selection in so that it
  460.  * can be dragged around.  If the current off-screen window is equal in
  461.  * size or larger than the passed size, then it is simply returned.
  462.  * Otherwise, it is resized to be the specified size.
  463.  */
  464. {
  465.     NXRect rect;
  466.     static id selectioncache = nil;
  467.  
  468.     if (!selectioncache) {
  469.     rect = bounds;
  470.     selectioncache = [Window newContent:&rect
  471.                       style:NX_PLAINSTYLE
  472.                     backing:NX_RETAINED
  473.                  buttonMask:0
  474.                       defer:NO];
  475.     [selectioncache reenableDisplay];
  476.     } else {
  477.     [selectioncache getFrame:&rect];
  478.     if (rect.size.width < bounds.size.width || rect.size.height < bounds.size.height) {
  479.         [selectioncache sizeWindow:MAX(rect.size.width, bounds.size.width)
  480.                       :MAX(rect.size.height, bounds.size.height)];
  481.     }
  482.     }
  483.  
  484.     return selectioncache;
  485. }
  486.  
  487. - getBBox:(NXRect *)bbox of:list extended:(BOOL)extended
  488. /*
  489.  * Returns a rectangle which encloses all the objects in the list.
  490.  */
  491. {
  492.     int i;
  493.     NXRect eb;
  494.  
  495.     i = [list count];
  496.     if (i) {
  497.     if (extended) {
  498.         [[list objectAt:--i] getExtendedBounds:bbox];
  499.         while (i--) NXUnionRect([[list objectAt:i] getExtendedBounds:&eb], bbox);
  500.     } else {
  501.         [[list objectAt:--i] getBounds:bbox];
  502.         while (i--) {
  503.         [[list objectAt:i] getBounds:&eb];
  504.         NXUnionRect(&eb, bbox);
  505.         }
  506.     }
  507.     } else {
  508.     bbox->size.width = bbox->size.height = 0.0;
  509.     }
  510.  
  511.     return self;
  512. }
  513.  
  514. - getBBox:(NXRect *)bbox of:list
  515. {
  516.     return [self getBBox:bbox of:list extended:YES];
  517. }
  518.  
  519. - recacheSelection
  520.  /*
  521.   * Redraws the selection in the off-screen cache (not the selection cache),
  522.   * then composites it back on to the screen.
  523.   */
  524. {
  525.     NXRect sbounds;
  526.     
  527.     if ([slist count]) {
  528.     [self getBBox:&sbounds of:slist];
  529.     gvFlags.cacheing = YES;
  530.     [self drawSelf:&sbounds :1];
  531.     gvFlags.cacheing = NO;
  532.     [self display:&sbounds :1];
  533.     }
  534.  
  535.     return self;
  536. }
  537.  
  538. - getSelection
  539. /*
  540.  * Resets slist by going through the glist and locating all the Graphics
  541.  * which respond YES to the isSelected method.
  542.  */
  543. {
  544.     id g;
  545.     int i;
  546.  
  547.     [slist free];
  548.     slist = [[List allocFromZone:[self zone]] init];
  549.     gvFlags.groupInSlist = NO;
  550.     i = [glist count];
  551.     while (i--) {
  552.     g = [glist objectAt:i];
  553.     if ([g isSelected]) {
  554.         [slist insertObject:g at:0];
  555.         gvFlags.groupInSlist = gvFlags.groupInSlist || [g isKindOf:[Group class]];
  556.     }
  557.     }
  558.  
  559.     return self;
  560. }
  561.  
  562. - saveSelection
  563. {
  564.     [saveList free];
  565.     saveList = [slist copy];
  566.     return self;
  567. }
  568.  
  569. - restoreSelection
  570. {
  571.         int i;
  572.     id g;
  573.     
  574.         [slist free];
  575.         slist = [[List allocFromZone:[self zone]] init];
  576.         gvFlags.groupInSlist = NO;
  577.         i = [saveList count];
  578.         while (i--) {
  579.         g = [saveList objectAt:i];
  580.         [g select];
  581.         [slist insertObject:g at:0];
  582.         gvFlags.groupInSlist = gvFlags.groupInSlist || [g isKindOf:[Group class]];
  583.     }
  584.         return self;
  585. }
  586.  
  587. - compositeSelection:(const NXRect *)sbounds from:(int)gstate
  588. /*
  589.  * Composites from the specified gstate whatever part of sbounds is
  590.  * currently visible in the View.
  591.  */
  592. {
  593.     PScomposite(0.0, 0.0, NX_WIDTH(sbounds), NX_HEIGHT(sbounds),
  594.                 gstate, NX_X(sbounds), NX_Y(sbounds), NX_SOVER);
  595.     [window flushWindow];
  596.     NXPing();
  597.     return self;
  598. }
  599.  
  600. - (int)cacheSelection
  601.  /*
  602.   * Caches the selection into the application-wide selection cache
  603.   * window (a window which has alpha in it).  See also: selectionCache:.
  604.   * It draws the objects without their knobbies in the selection cache,
  605.   * but it leaves them selected.  Returns the gstate of the selection
  606.   * cache.
  607.   */
  608. {
  609.     int i;
  610.     NXRect sbounds;
  611.     id selectionCache;
  612.  
  613.     [self getBBox:&sbounds of:slist];
  614.     selectionCache = [self selectionCache];
  615.     [[selectionCache contentView] lockFocus];
  616.     PSsetgray(NX_WHITE);
  617.     PSsetalpha(0.0);    /* fully transparent */
  618.     PStranslate(- sbounds.origin.x, - sbounds.origin.y);
  619.     sbounds.size.width += 1.0;
  620.     sbounds.size.height += 1.0;
  621.     NXRectFill(&sbounds);
  622.     sbounds.size.width -= 1.0;
  623.     sbounds.size.height -= 1.0;
  624.     PSsetalpha(1.0);    /* fully opaque */
  625.     i = [slist count];
  626.     while (i--) [[[[slist objectAt:i] deselect] draw:NULL] select];
  627.     [Graphic showFastRectFills];
  628.     PStranslate(sbounds.origin.x, sbounds.origin.y);
  629.     [[selectionCache contentView] unlockFocus];
  630.  
  631.     return [selectionCache gState];
  632. }
  633.  
  634. /* Other private methods. */
  635.  
  636. - cacheGraphic:graphic
  637.  /*
  638.   * Draws the graphic into the off-screen cache, then composites
  639.   * it back to the screen.
  640.   * NOTE: This ONLY works if the graphic is on top of the list!
  641.   * That is why it is a private method ...
  642.   */
  643. {
  644.     NXRect eb;
  645.  
  646.     [[cacheWindow contentView] lockFocus];
  647.     [graphic draw:NULL];
  648.     [Graphic showFastRectFills];
  649.     [[cacheWindow contentView] unlockFocus];
  650.     [self display:[graphic getExtendedBounds:&eb] :1];
  651.  
  652.     return self;
  653. }
  654.  
  655. - resetGUP
  656. /*
  657.  * The "GUP" is the Grid User Path.  It is a user path which draws a grid
  658.  * the size of the bounds of the GraphicView.  This gets called whenever
  659.  * the View is resized or the grid spacing is changed.  It sets up all
  660.  * the arguments to DPSDoUserPath() called in drawSelf::.
  661.  */
  662. {
  663.     int x, y, i, j;
  664.     short w, h;
  665.  
  666.     if (gvFlags.grid < 4) return self;
  667.  
  668.     x = (int)bounds.size.width / gvFlags.grid;
  669.     y = (int)bounds.size.height / gvFlags.grid;
  670.     gupLength = (x << 2) + (y << 2);
  671.     if (gupCoords) {
  672.     NX_FREE(gupCoords);
  673.     NX_FREE(gupOps);
  674.     NX_FREE(gupBBox);
  675.     }
  676.     NX_ZONEMALLOC([self zone], gupCoords, short, gupLength);
  677.     NX_ZONEMALLOC([self zone], gupOps, char, gupLength >> 1);
  678.     NX_ZONEMALLOC([self zone], gupBBox, short, 4);
  679.     w = bounds.size.width;
  680.     h = bounds.size.height;
  681.     j = 0;
  682.     for (i = 1; i <= y; i++) {
  683.     gupCoords[j++] = 0.0;
  684.     gupCoords[j++] = i * gvFlags.grid;
  685.     gupCoords[j++] = w;
  686.     gupCoords[j] = gupCoords[j-2];
  687.     j++;
  688.     }
  689.     for (i = 1; i <= x; i++) {
  690.     gupCoords[j++] = i * gvFlags.grid;
  691.     gupCoords[j++] = 0.0;
  692.     gupCoords[j] = gupCoords[j-2];
  693.     j++;
  694.     gupCoords[j++] = h;
  695.     }
  696.     i = gupLength >> 1;
  697.     while (i) {
  698.     gupOps[--i] = dps_lineto;
  699.     gupOps[--i] = dps_moveto;
  700.     }
  701.     gupBBox[0] = gupBBox[1] = 0;
  702.     gupBBox[2] = bounds.size.width + 1;
  703.     gupBBox[3] = bounds.size.height + 1;
  704.  
  705.     return self;
  706. }
  707.  
  708. #define MOVE_MASK NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK
  709.  
  710. - (BOOL)move:(NXEvent *)event
  711. /*
  712.  * Moves the selection by cacheing the selected graphics into the
  713.  * selection cache, then compositing them repeatedly as the user
  714.  * moves the mouse.  The tracking loop uses TIMER events to autoscroll
  715.  * at regular intervals.  TIMER events do not have valid mouse coordinates,
  716.  * so the last coordinates are saved and restore when there is a TIMER event.
  717.  */
  718. {
  719.     int gstate;
  720.     NXEvent peek;
  721.     NXCoord dx, dy;
  722.     NXTrackingTimer *timer = NULL;
  723.     NXPoint p, start, last, sboundspad;
  724.     NXRect minbounds, sbounds, visibleRect;
  725.     BOOL canScroll, tracking = YES, alternate, horizConstrain = NO, vertConstrain = NO;
  726.  
  727.     last = event->location;
  728.     alternate = (event->flags & NX_ALTERNATEMASK) ? YES : NO;
  729.  
  730.     event = [NXApp getNextEvent:MOVE_MASK];
  731.     if (event->type == NX_MOUSEUP) return NO;
  732.  
  733. //    PShidecursor();
  734.  
  735.     [self convertPoint:&last fromView:nil];
  736.     [self grid:&last];
  737.  
  738.     [self lockFocus];
  739.  
  740.     gstate = [self cacheSelection];
  741.     [self graphicsPerform:@selector(deactivate) andDraw:YES];
  742.     [self getBBox:&sbounds of:slist];
  743.     [self getBBox:&minbounds of:slist extended:NO];
  744.     sboundspad.x = minbounds.origin.x - sbounds.origin.x;
  745.     sboundspad.y = minbounds.origin.y - sbounds.origin.y;
  746.     [self compositeSelection:&sbounds from:gstate];
  747.  
  748.     [self getVisibleRect:&visibleRect];
  749.     canScroll = !NXEqualRect(&visibleRect, &bounds);
  750.  
  751.     start = sbounds.origin;
  752.  
  753.     while (tracking) {
  754.     p = event->location;
  755.     [self convertPoint:&p fromView:nil];
  756.     [self grid:&p];
  757.     dx = p.x - last.x;
  758.     dy = p.y - last.y;
  759.     if (dx || dy) {
  760.         [self drawSelf:&sbounds :1];
  761.         if (alternate && (dx || dy)) {
  762.         if (ABS(dx) > ABS(dy)) {
  763.             horizConstrain = YES;
  764.             dy = 0.0;
  765.         } else {
  766.             vertConstrain = YES;
  767.             dx = 0.0;
  768.         }
  769.         alternate = NO;
  770.         } else if (horizConstrain) {
  771.         dy = 0.0;
  772.         } else if (vertConstrain) {
  773.         dx = 0.0;
  774.         }
  775.         NXOffsetRect(&sbounds, dx, dy);
  776.         minbounds.origin.x = sbounds.origin.x + sboundspad.x;
  777.         minbounds.origin.y = sbounds.origin.y + sboundspad.y;
  778.         [self tryToPerform:@selector(updateRulers:) with:(void *)&minbounds];
  779.         if (!canScroll || NXContainsRect(&visibleRect, &sbounds)) {
  780.         [self compositeSelection:&sbounds from:gstate];
  781.         stopTimer(timer);
  782.         }
  783.         last = p;
  784.     }
  785.     tracking = (event->type != NX_MOUSEUP);
  786.     if (tracking) {
  787.         if (canScroll && !NXContainsRect(&visibleRect, &sbounds)) {
  788.         [window disableFlushWindow];
  789.         [self scrollRectToVisible:&sbounds];
  790.         [self getVisibleRect:&visibleRect];
  791.         [self compositeSelection:&sbounds from:gstate];
  792.         [[window reenableFlushWindow] flushWindow];
  793.         startTimer(timer);
  794.         }
  795.         p = event->location;
  796.         if (![NXApp peekNextEvent:MOVE_MASK into:&peek]) {
  797.         event = [NXApp getNextEvent:MOVE_MASK|NX_TIMERMASK];
  798.         } else {
  799.         event = [NXApp getNextEvent:MOVE_MASK];
  800.         }
  801.         if (event->type == NX_TIMER) event->location = p;
  802.     }
  803.     }
  804.  
  805.     if (canScroll) stopTimer(timer);
  806.  
  807. //    PSshowcursor();
  808.  
  809.     p.x = sbounds.origin.x - start.x;
  810.     p.y = sbounds.origin.y - start.y;
  811.     if (p.x || p.y) [self graphicsPerform:@selector(moveBy:) with:(id)&p andDraw:NO];
  812.  
  813.     [self graphicsPerform:@selector(activate) andDraw:YES];
  814.  
  815.     [self tryToPerform:@selector(updateRulers:) with:nil];
  816.  
  817.     [window flushWindow];
  818.     [self unlockFocus];
  819.  
  820.     return YES;
  821. }
  822.  
  823. #define DRAG_MASK (NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK|NX_TIMERMASK)
  824.  
  825. - dragSelect:(NXEvent *)event
  826. /*
  827.  * Allows the user the drag out a box to select all objects either
  828.  * intersecting the box, or fully contained within the box (depending
  829.  * on the state of the ALTERNATE key).  After the selection is made,
  830.  * the slist is updated.
  831.  */
  832. {
  833.     id g;
  834.     int i;
  835.     NXPoint p, last, start;
  836.     NXTrackingTimer *timer = NULL;
  837.     BOOL mustContain, shift, canScroll;
  838.     NXRect visibleRect, eb, region, oldRegion;
  839.  
  840.     p = start = event->location;
  841.     [self convertPoint:&start fromView:nil];
  842.     last = start;
  843.  
  844.     shift = (event->flags & NX_SHIFTMASK) ? YES : NO;
  845.     mustContain = (event->flags & NX_ALTERNATEMASK) ? YES : NO;
  846.  
  847.     [self lockFocus];
  848.  
  849.     [self getVisibleRect:&visibleRect];
  850.     canScroll = !NXEqualRect(&visibleRect, &bounds);
  851.     if (canScroll) startTimer(timer);
  852.  
  853.     PSsetgray(NX_LTGRAY);
  854.     PSsetlinewidth(0.0);
  855.  
  856.     event = [NXApp getNextEvent:DRAG_MASK];
  857.     while (event->type != NX_MOUSEUP) {
  858.     if (event->type == NX_TIMER) event->location = p;
  859.     p = event->location;
  860.     [self convertPoint:&p fromView:nil];
  861.     if (p.x != last.x || p.y != last.y) {
  862.         getRegion(®ion, &p, &start);
  863.         NXInsetRect(&oldRegion, -1.0, -1.0);
  864.         [window disableFlushWindow];
  865.         [self drawSelf:&oldRegion :1];
  866.         if (canScroll) {
  867.         [self scrollRectToVisible:®ion];
  868.         [self scrollPointToVisible:&p];
  869.         }
  870.         PSrectstroke(region.origin.x, region.origin.y, region.size.width, region.size.height);
  871.         [self tryToPerform:@selector(updateRulers:) with:(void *)®ion];
  872.         [[window reenableFlushWindow] flushWindow];
  873.         oldRegion = region;
  874.         last = p;
  875.         NXPing();
  876.     }
  877.     p = event->location;
  878.     event = [NXApp getNextEvent:DRAG_MASK];
  879.     }
  880.  
  881.     if (canScroll) stopTimer(timer);
  882.  
  883.     for (i = [glist count] - 1; i >= 0; i--) {
  884.      g = [glist objectAt:i];
  885.     [g getExtendedBounds:&eb];
  886.     if (![g isLocked] && ![g isSelected] && 
  887.         ((mustContain && NXContainsRect(®ion, &eb)) ||
  888.          (!mustContain && NXIntersectsRect(®ion, &eb)))) {
  889.         [slist insertObject:[g select] at:0];
  890.         gvFlags.groupInSlist = gvFlags.groupInSlist || [g isKindOf:[Group class]];
  891.     }
  892.     }
  893.  
  894.     NXInsetRect(®ion, -1.0, -1.0);
  895.     [self drawSelf:®ion :1];
  896.     [self recacheSelection];
  897.  
  898.     [self tryToPerform:@selector(updateRulers:) with:nil];
  899.  
  900.     [self unlockFocus];
  901.  
  902.     return self;
  903. }
  904.  
  905. /* Public interface methods. */
  906.  
  907. - dirty
  908. {
  909.     gvFlags.dirty = YES;
  910.     [window setDocEdited:YES];
  911.     return self;
  912. }
  913.  
  914. - (BOOL)isDirty
  915. {
  916.     return gvFlags.dirty;
  917. }
  918.  
  919. - (BOOL)isEmpty
  920. {
  921.     return [glist count] == 0;
  922. }
  923.  
  924. - (BOOL)hasEmptySelection
  925. {
  926.     return [slist count] == 0;
  927. }
  928.  
  929. - graphicsPerform:(SEL)aSelector andDraw:(BOOL)flag
  930. /*
  931.  * Performs the given aSelector on each member of the slist, then
  932.  * recaches and redraws the larger of the area covered by the objects before
  933.  * the selector was applied and the area covered by the objects after the
  934.  * selector was applied.  If you want to perform a method on each item
  935.  * in the slist and NOT redraw, then use the List method makeObjectsPerform:.
  936.  */
  937. {
  938.     id g;
  939.     int i, count;
  940.     NXRect eb, affectedBounds;
  941.  
  942.     if (flag) {
  943.     count = [slist count];
  944.     if (count) {
  945.         [[slist objectAt:0] getExtendedBounds:&affectedBounds];
  946.         for (i = 1; i < count; i++) {
  947.         g = [slist objectAt:i];
  948.         NXUnionRect([g getExtendedBounds:&eb], &affectedBounds);
  949.         }
  950.         for (i = 0; i < count; i++) {
  951.         g = [slist objectAt:i];
  952.         [g perform:aSelector];
  953.         NXUnionRect([g getExtendedBounds:&eb], &affectedBounds);
  954.         }
  955.         [self cache:&affectedBounds];
  956.     }    
  957.     } else {
  958.     [slist makeObjectsPerform:aSelector];
  959.     }
  960.  
  961.     return self;
  962. }
  963.  
  964. - graphicsPerform:(SEL)aSelector with:(void *)argument andDraw:(BOOL)flag
  965. {
  966.     id g;
  967.     int i, count;
  968.     NXRect eb, affectedBounds;
  969.  
  970.     if (flag) {
  971.     count = [slist count];
  972.     if (count) {
  973.         [[slist objectAt:0] getExtendedBounds:&affectedBounds];
  974.         for (i = 1; i < count; i++) {
  975.         g = [slist objectAt:i];
  976.         NXUnionRect([g getExtendedBounds:&eb], &affectedBounds);
  977.         }
  978.         for (i = 0; i < count; i++) {
  979.         g = [slist objectAt:i];
  980.         [g perform:aSelector with:argument];
  981.         NXUnionRect([g getExtendedBounds:&eb], &affectedBounds);
  982.         }
  983.         [self cache:&affectedBounds];
  984.     }
  985.     } else {
  986.     [slist makeObjectsPerform:aSelector with:argument];
  987.     }
  988.  
  989.     return self;
  990. }
  991.  
  992. - graphicsPerformSingle:(SEL)aSelector with:(void *)argument on:(id) g
  993. {
  994.     NXRect  affectedBounds;
  995.  
  996.             [g getExtendedBounds:&affectedBounds];
  997.         [g perform:aSelector with:argument];
  998.             [self cache:&affectedBounds];
  999.  
  1000.     return self;
  1001. }
  1002.  
  1003. - cache:(const NXRect *)rect
  1004. /*
  1005.  * Draws all the Graphics intersected by rect into the off-screen cache,
  1006.  * then composites the rect back to the screen (but does NOT flushWindow).
  1007.  */
  1008. {
  1009.     gvFlags.cacheing = YES;
  1010.     [self drawSelf:rect :1];
  1011.     gvFlags.cacheing = NO;
  1012.     if ([self canDraw]) {
  1013.     [self lockFocus];
  1014.     [self drawSelf:rect :1];
  1015.     [self unlockFocus];
  1016.     }
  1017.     return self;
  1018. }
  1019.  
  1020. - insertGraphic:graphic
  1021. /*
  1022.  * Inserts the specified graphic into the glist and draws it.
  1023.  * The new graphic will join the selection, not replace it.
  1024.  */
  1025. {
  1026.     NXRect eb;
  1027.  
  1028.     if (graphic) {
  1029.     [slist insertObject:graphic at:0];
  1030.     [glist insertObject:graphic at:0];
  1031.     [self cache:[graphic getExtendedBounds:&eb]];
  1032.     if ([graphic isKindOf:[Group class]]) gvFlags.groupInSlist = YES;
  1033.     [window flushWindow];
  1034.     DIRTY(YES);
  1035.     }
  1036.  
  1037.     return self;
  1038. }
  1039.  
  1040. - insertGraphicNoSelect:graphic
  1041. /*
  1042.  * Inserts the specified graphic into the glist and draws it.
  1043.  */
  1044. {
  1045.     NXRect eb;
  1046.  
  1047.     if (graphic) {
  1048.     [graphic deselect];
  1049.     [glist insertObject:graphic at:0];
  1050.     [self cache:[graphic getExtendedBounds:&eb]];
  1051.     [window flushWindow];
  1052.     DIRTY(YES);
  1053.     }
  1054.  
  1055.     return self;
  1056. }
  1057.  
  1058. - removeGraphic:graphic
  1059. /*
  1060.  * Removes the graphic from the GraphicView and redraws.
  1061.  */
  1062. {
  1063.     int i;
  1064.     NXRect eb;
  1065.     id g = nil;
  1066.  
  1067.     if (!graphic) return self;
  1068.  
  1069.     i = [glist count];
  1070.     while (g != graphic && i--) g = [glist objectAt:i];
  1071.     if (g == graphic) {
  1072.     [g getExtendedBounds:&eb];
  1073.     [glist removeObjectAt:i];
  1074.     [slist removeObject:g];
  1075.     if ([g isKindOf:[Group class]]) gvFlags.groupInSlist = checkForGroup(slist);
  1076.     [self cache:&eb];
  1077.     [window flushWindow];
  1078.     DIRTY(YES);
  1079.     [NXApp delayedFree:g];
  1080.     }
  1081.  
  1082.     return self;
  1083. }
  1084.  
  1085. - selectedGraphic
  1086. /*
  1087.  * If there is one and only one Graphic selected, this method returns it.
  1088.  */
  1089. {
  1090.     if ([slist count] == 1) {
  1091.     id g = [slist objectAt:0];
  1092.     return [g isKindOf:[Group class]] ? nil : g;
  1093.     } else {
  1094.     return nil;
  1095.     }
  1096. }
  1097.  
  1098. /* Methods to modify the grid of the GraphicView. */
  1099.  
  1100. - (int)gridSpacing
  1101. {
  1102.     return gvFlags.gridDisabled ? 1 : gvFlags.grid;
  1103. }
  1104.  
  1105. - (BOOL)gridIsVisible
  1106. {
  1107.     return gvFlags.showGrid;
  1108. }
  1109.  
  1110. - (BOOL)gridIsEnabled
  1111. {
  1112.     return !gvFlags.gridDisabled;
  1113. }
  1114.  
  1115. - (float)gridGray
  1116. {
  1117.     return gridGray;
  1118. }
  1119.  
  1120. - setGridSpacing:(int)gridSpacing
  1121. {
  1122.     if (gridSpacing != gvFlags.grid && gridSpacing > 0 && gridSpacing < 256) {
  1123.     gvFlags.grid = gridSpacing;
  1124.     if (gvFlags.showGrid) {
  1125.         [self resetGUP];
  1126.         [self cache:&bounds];
  1127.         [window flushWindow];
  1128.     }
  1129.     }
  1130.     return self;
  1131. }
  1132.  
  1133. - setGridEnabled:(BOOL)flag
  1134. {
  1135.     gvFlags.gridDisabled = flag ? NO : YES;
  1136.     return self;
  1137. }
  1138.  
  1139. - setGridVisible:(BOOL)flag
  1140. {
  1141.     if (gvFlags.showGrid != flag) {
  1142.     gvFlags.showGrid = flag;
  1143.     if (flag) [self resetGUP];
  1144.     [self cache:&bounds];
  1145.     [window flushWindow];
  1146.     }
  1147.     return self;
  1148. }
  1149.  
  1150. - setGridGray:(float)gray
  1151. {
  1152.     if (gray != gridGray) {
  1153.     gridGray = gray;
  1154.     if (gvFlags.showGrid) {
  1155.         [self cache:&bounds];
  1156.         [window flushWindow];
  1157.     }
  1158.     }
  1159.     return self;
  1160. }
  1161.  
  1162. - setGridSpacing:(int)gridSpacing andGray:(float)gray
  1163. {
  1164.     if (gray != gridGray || (gridSpacing != gvFlags.grid && gridSpacing > 0 && gridSpacing < 256)) {
  1165.     gridGray = gray;
  1166.     if (gvFlags.grid != gridSpacing && gridSpacing > 0 && gridSpacing < 256) {
  1167.         gvFlags.grid = gridSpacing;
  1168.         if (gvFlags.showGrid) [self resetGUP];
  1169.     }
  1170.     if (gvFlags.showGrid) {
  1171.         [self cache:&bounds];
  1172.         [window flushWindow];
  1173.     }
  1174.     }
  1175.     return self;
  1176. }
  1177.  
  1178. - grid:(NXPoint *)p
  1179. {
  1180.     grid(*p);
  1181.     return self;
  1182. }
  1183.  
  1184. /* Public methods for importing foreign data types into the GraphicView */
  1185.  
  1186. - placeGraphic:graphic at:(const NXPoint *)location
  1187. /*
  1188.  * Places the graphic centered at the given location on the page.
  1189.  * If the graphic is too big, the user is asked whether the graphic
  1190.  * should be scaled.
  1191.  */
  1192. {
  1193.     int scale;
  1194.     NXPoint offset;
  1195.     float sx, sy, factor;
  1196.     NXRect gbounds, myBounds;
  1197.  
  1198.     if (graphic) {
  1199.     [graphic getExtendedBounds:&gbounds];
  1200.     if (gbounds.size.width > bounds.size.width || gbounds.size.height > bounds.size.height) {
  1201.         scale = NXRunAlertPanel("Load Image",
  1202.         "The image is too large to fit on the page.  Scale it to fit?",
  1203.         "Scale", "Don't Scale", "Cancel");
  1204.         if (scale < 0) {
  1205.         [graphic free];
  1206.         return self;
  1207.         } else if (scale > 0) {
  1208.         sx = (bounds.size.width / gbounds.size.width) * 0.95;
  1209.         sy = (bounds.size.height / gbounds.size.height) * 0.95;
  1210.         factor = MIN(sx, sy);
  1211.         gbounds.size.width *= factor;
  1212.         gbounds.size.height *= factor;
  1213.         [graphic sizeTo:&gbounds.size];
  1214.         }
  1215.     }
  1216.     if (location) [graphic centerAt:location];
  1217.     [graphic getExtendedBounds:&gbounds];
  1218.     myBounds = bounds;
  1219.     NXContainRect(&myBounds, &gbounds);
  1220.     offset.x = bounds.origin.x - myBounds.origin.x;
  1221.     offset.y = bounds.origin.y - myBounds.origin.y;
  1222.     if (offset.x || offset.y) [graphic moveBy:&offset];
  1223.     [self deselectAll:self];
  1224.     [self insertGraphic:graphic];
  1225.     [self scrollGraphicToVisible:graphic];
  1226.     }
  1227.  
  1228.     return graphic;
  1229. }
  1230.  
  1231. - loadImageFromStream:(NXStream *)stream at:(const NXPoint *)location allowAlpha:(BOOL)alphaOk
  1232. /*
  1233.  * Creates a new Image object using the PostScript or TIFF found in the
  1234.  * given stream and inserts it as the only item in the selection (i.e.
  1235.  * it deslects everything else).   The new Graphic is centered at the
  1236.  * given point p, and the GraphicView is scrolled to make the Graphic
  1237.  * visible.
  1238.  */
  1239. {
  1240.     return [self placeGraphic:[[Image allocFromZone:[self zone]] initFromStream:stream allowAlpha:alphaOk] at:location];
  1241. }
  1242.  
  1243.  
  1244. /* Writing data in different forms (other than the internal Draw format) */
  1245.  
  1246. - writePSToStream:(NXStream *)stream
  1247. /*
  1248.  * Writes out the PostScript generated by drawing all the objects in the
  1249.  * glist.  The bounding box of the generated encapsulated PostScript will
  1250.  * be equal to the bounding box of the objects in the glist (not the
  1251.  * bounds of the view).
  1252.  */
  1253. {
  1254.     NXRect bbox;
  1255.  
  1256.     if (stream) {
  1257.     [self getBBox:&bbox of:glist];
  1258.     [self copyPSCodeInside:&bbox to:stream];
  1259.     }
  1260.  
  1261.     return self;
  1262. }
  1263.  
  1264. - writeTIFFToStream:(NXStream *)stream
  1265. /*
  1266.  * Images all of the objects in the glist and writes out the result in
  1267.  * the Tagged Image File Format (TIFF).  The image WILL have alpha in it.
  1268.  */
  1269. {
  1270.     int size;
  1271.     NXRect sbounds;
  1272.     NXImageInfo image;
  1273.     unsigned char *data;
  1274.     id cache, savedslist;
  1275.  
  1276.     if (!stream) return self;
  1277.  
  1278.     savedslist = slist;
  1279.     slist = glist;
  1280.     [self cacheSelection];
  1281.     [self getBBox:&sbounds of:slist];
  1282.     slist = savedslist;
  1283.     cache = [[self selectionCache] contentView];
  1284.     [cache lockFocus];
  1285.     sbounds.origin.x = sbounds.origin.y = 0.0;
  1286.     NXSizeBitmap(&sbounds, &size, &image.width, &image.height,
  1287.          &image.bitsPerSample, &image.samplesPerPixel,
  1288.          &image.planarConfig, &image.photoInterp);
  1289.     NX_MALLOC(data, unsigned char, size);
  1290.     NXReadBitmap(&sbounds, image.width, image.height, image.bitsPerSample,
  1291.     image.samplesPerPixel, image.planarConfig, image.photoInterp,
  1292.     data, data + (size >> 1), NULL, NULL, NULL);
  1293.     [cache unlockFocus];
  1294.     NXWriteTIFF(stream, &image, data);
  1295.     NX_FREE(data);
  1296.  
  1297.     return self;
  1298. }
  1299.  
  1300.  
  1301. /* Methods overridden from superclass. */
  1302.  
  1303. - sizeTo:(NXCoord)width :(NXCoord)height
  1304. /*
  1305.  * Overrides View's sizeTo:: so that the cacheWindow is resized when
  1306.  * the View is resized.
  1307.  */
  1308. {
  1309.     if (width != bounds.size.width || height != bounds.size.height) {
  1310.     [super sizeTo:width :height];
  1311.     [cacheWindow free];
  1312.     cacheWindow = createCache(&bounds.size, [self zone]);
  1313.     [self resetGUP];
  1314.     [self cache:&bounds];
  1315.     }
  1316.     return self;
  1317. }
  1318.  
  1319. - mouseDown:(NXEvent *)event
  1320. /*
  1321.  * This method handles a mouse down.
  1322.  *
  1323.  * If a current tool is in effect, then the mouse down causes a new
  1324.  * Graphic to begin being created.  Otherwise, the selection is modified
  1325.  * either by adding elements to it or removing elements from it, or moving
  1326.  * it.  Here are the rules:
  1327.  *
  1328.  * Tool in effect
  1329.  *    Shift OFF
  1330.  *    create a new Graphic which becomes the new selection
  1331.  *    Shift ON
  1332.  *    create a new Graphic and ADD it to the current selection
  1333.  *    Control ON
  1334.  *    leave creation mode, and start selection
  1335.  * Otherwise
  1336.  *    Shift OFF
  1337.  *    a. Click on a selected Graphic -> select graphic further back
  1338.  *    b. Click on an unselected Graphic -> that Graphic becomes selection
  1339.  *    Shift ON
  1340.  *    a. Click on a selected Graphic -> remove it from selection
  1341.  *    b. Click on unselected Graphic -> add it to selection
  1342.  *    Alternate ON
  1343.  *    if no affected graphic, causes drag select to select only objects
  1344.  *    completely contained within the dragged box.
  1345.  *
  1346.  * Essentially, everything works as one might expect except the ability to
  1347.  * select a Graphic which is deeper in the list (i.e. further toward the
  1348.  * back) by clicking on the currently selected Graphic.
  1349.  *
  1350.  * This is a very hairy mouseDown:.  Most need not be this scary.
  1351.  */
  1352. {
  1353.     NXPoint p;
  1354.     NXRect eb;
  1355.     int i, corner, oldMask;
  1356.     id factory, g = nil, startg = nil;
  1357.     BOOL shift, control, gotHit = NO, deepHit = NO;
  1358.  
  1359.     /*
  1360.      * You only need to do the following line in a mouseDown: method if
  1361.      * you receive this message because one of your subviews gets the
  1362.      * mouseDown: and does not respond to it (thus, it gets passed up the
  1363.      * responder chain to you).  In this case, our editView receives the
  1364.      * mouseDown:, but doesn't do anything about it, and when it comes
  1365.      * to us, we want to become the first responder.
  1366.      *
  1367.      * Normally you won't have a subview which doesn't do anything with
  1368.      * mouseDown:, in which case, you need only return YES from the
  1369.      * method acceptsFirstResponder (see that method below) and will NOT
  1370.      * need to do the following makeFirstResponder:.  In other words,
  1371.      * don't put the following line in your mouseDown: implementation!
  1372.      *
  1373.      * Sorry about confusing this issue ... 
  1374.      */
  1375.   
  1376.     if ([window firstResponder] != self) [window makeFirstResponder:self];
  1377.  
  1378.     shift = (event->flags & NX_SHIFTMASK) ? YES : NO;
  1379.     control = (event->flags & NX_CONTROLMASK) ? YES : NO;
  1380.  
  1381.     p = event->location;
  1382.     [self convertPoint:&p fromView:nil];
  1383.  
  1384.     oldMask = [window addToEventMask:NX_MOUSEDRAGGEDMASK|NX_MOUSEUPMASK];
  1385.  
  1386.     factory = [self currentGraphic];
  1387.     if (!control && factory) {
  1388.     if ([factory isEditable]) {    /* if editable, try to edit one */
  1389.         i = 0;
  1390.         g = [glist objectAt:i++];
  1391.         while (g != nil) {
  1392.         if ([g isKindOf:factory] && [g hit:&p]) {
  1393.             if ([g isSelected]) {
  1394.             [g deselect];
  1395.             [self cache:[g getExtendedBounds:&eb]];
  1396.             [slist removeObject:g];
  1397.             if ([g isKindOf:[Group class]]) gvFlags.groupInSlist = checkForGroup(slist);
  1398.             DIRTY(YES);
  1399.             }
  1400.             [g edit:event in:editView];
  1401.             break;
  1402.         }
  1403.         g = [glist objectAt:i++];
  1404.         }
  1405.     }
  1406.     if (!g) {    /* not editing or no editable graphic found */
  1407.         g = [[factory allocFromZone:[self zone]] init];
  1408.         if ([NXApp respondsTo:@selector(inspectorPanel)]) {
  1409.         [[[NXApp inspectorPanel] delegate] initializeGraphic:g];
  1410.         }
  1411.         if ([g create:event in:self]) {
  1412.         if (!shift) [self deselectAll:self];
  1413.         [glist insertObject:g at:0];
  1414.         if ([g isSelected]) [slist insertObject:g at:0];
  1415.         gvFlags.groupInSlist = gvFlags.groupInSlist || [g isKindOf:[Group class]];
  1416.         [self cacheGraphic:g];
  1417.         [g edit:NULL in:editView];
  1418.         DIRTY(YES);
  1419.         } else {
  1420.         [g free];
  1421.         }
  1422.     }
  1423.     } else {        /* selecting/resizing/moving */
  1424.     i = 0;
  1425.     g = [glist objectAt:i++];
  1426.     while (g != nil && !gotHit) {
  1427.         corner = [g knobHit:&p];
  1428.         if (corner > 0) {            /* corner hit */
  1429.         gotHit = YES;
  1430.         [g resize:event by:corner in:self];
  1431.         DIRTY(YES);
  1432.         } else if (corner) {        /* complete miss */
  1433.         g = [glist objectAt:i++];
  1434.         } else g = nil;            /* non-corner opaque hit */
  1435.     }
  1436.     i = 0;
  1437.     if (!gotHit) g = [glist objectAt:i++];
  1438.     while (g && !gotHit && !deepHit) {
  1439.         if ([g isSelected] && [g hit:&p]) {
  1440.         if (shift) {
  1441.             gotHit = YES;
  1442.             [g deselect];
  1443.             [self cache:[g getExtendedBounds:&eb]];
  1444.             [slist removeObject:g];
  1445.             if ([g isKindOf:[Group class]]) gvFlags.groupInSlist = checkForGroup(slist);
  1446.             DIRTY(YES);
  1447.         } else {
  1448.             gotHit = [self move:event];
  1449.             if (!gotHit) {
  1450.             deepHit = ![g isOpaque];
  1451.             if (!deepHit) gotHit = YES;
  1452.             } else {
  1453.             DIRTY(YES);
  1454.             }
  1455.         }
  1456.         }
  1457.         g = [glist objectAt:i++];
  1458.     }
  1459.     startg = g;
  1460.     if (!gotHit) do {
  1461.         if (!g) {
  1462.         i = 0;
  1463.         g = [glist objectAt:i++];
  1464.         }
  1465.         if (![g isSelected] && [g hit:&p]) {
  1466.         gotHit = YES;
  1467.         if (!shift) {
  1468.             [self deselectAll:self];
  1469.             [slist addObject:g];
  1470.             gvFlags.groupInSlist = [g isKindOf:[Group class]];
  1471.         }
  1472.         [g select];
  1473.         DIRTY(YES);
  1474.         if (shift) [self getSelection];
  1475.         if (deepHit || ![self move:event]) [self cache:[g getExtendedBounds:&eb]];
  1476.         } else {
  1477.         g = [glist objectAt:i++];
  1478.         }
  1479.     } while (!gotHit && g != startg);
  1480.  
  1481.     if (!gotHit && !deepHit) {
  1482.         if (!shift) {
  1483.         DIRTY([slist count] > 0);
  1484.         [self lockFocus];
  1485.         [self deselectAll:self];
  1486.         [self unlockFocus];
  1487.         }
  1488.         [self dragSelect:event];
  1489.     }
  1490.     }
  1491.  
  1492.     [window flushWindow];
  1493.     [window setEventMask:oldMask];
  1494.  
  1495.     return self;
  1496. }
  1497.  
  1498. - drawSelf:(const NXRect *)rects :(int)rectCount
  1499. /*
  1500.  * Draws the GraphicView.
  1501.  *
  1502.  * If cacheing is on or if NXDrawingStatus != NX_DRAWING, then all the
  1503.  * graphics which intersect the specified rectangles will be drawn (and
  1504.  * clipped to those rectangles).  Otherwise, the specified rectangles
  1505.  * are composited to the screen from the off-screen cache.
  1506.  */
  1507. {
  1508.     NXRect *rp;
  1509.     NXRect r, visibleRect;
  1510.     int i, j, gstate;
  1511.  
  1512.     if (rects == NULL) return self;
  1513.  
  1514.     if (gvFlags.cacheing || NXDrawingStatus != NX_DRAWING) {
  1515.     if (NXDrawingStatus == NX_DRAWING) {
  1516.         [[cacheWindow contentView] lockFocus];
  1517.         PSsetgray(NX_WHITE);
  1518.         for (j = (rectCount > 1) ? 1 : 0; j < rectCount; j++) {
  1519.         NXRectFill(&rects[j]);
  1520.         if (gvFlags.showGrid && gvFlags.grid >= 4) {
  1521.             NXRectClip(&rects[j]);
  1522.             PSsetlinewidth(0.0);
  1523.             PSsetgray(gridGray);
  1524.             DPSDoUserPath(gupCoords, gupLength, dps_short,
  1525.                   gupOps, gupLength >> 1, gupBBox, dps_ustroke);
  1526.             PSsetgray(NX_WHITE);
  1527.         }
  1528.         }
  1529.     }
  1530.     for (j = (rectCount > 1) ? 1 : 0; j < rectCount; j++) {
  1531.         NXRectClip(&rects[j]);
  1532.         i = [glist count];
  1533.         while (i--) [[glist objectAt:i] draw:&rects[j]];
  1534.     }
  1535.     [Graphic showFastRectFills];
  1536.     if (NXDrawingStatus == NX_DRAWING) [[cacheWindow contentView] unlockFocus];
  1537.     }
  1538.  
  1539.     if (!gvFlags.cacheing && NXDrawingStatus == NX_DRAWING) {
  1540.     gstate = [cacheWindow gState];
  1541.     [self getVisibleRect:&visibleRect];
  1542.     for (j = 0; j < rectCount; j++) {
  1543.         rp = &r;
  1544.         r = rects[j];
  1545.         if (!NXEqualRect(rp, &visibleRect)) rp = NXIntersectionRect(&visibleRect, rp);
  1546.         if (rp) {
  1547.         PScomposite(NX_X(rp), NX_Y(rp), NX_WIDTH(rp), NX_HEIGHT(rp), gstate,
  1548.                 NX_X(rp), NX_Y(rp), NX_SOVER);
  1549.         }
  1550.     }
  1551.     }
  1552.  
  1553.     return self;
  1554. }
  1555.  
  1556. - keyDown:(NXEvent *)event
  1557. /*
  1558.  * Handles one of the arrow keys being pressed.
  1559.  * Note that since it might take a while to actually move the selection
  1560.  * (if it is large), we check to see if a bunch of arrow key events have
  1561.  * stacked up and move them all at once.
  1562.  */
  1563. {
  1564.     NXPoint p;
  1565.     NXEvent e;
  1566.     NXCoord delta;
  1567.     BOOL gotOne, first;
  1568.     NXEvent* eptr = event;
  1569.  
  1570.     if ((event->data.key.charSet != NX_ASCIISET ||
  1571.      event->data.key.charCode != 127) &&
  1572.     (event->data.key.charSet != NX_SYMBOLSET ||
  1573.      (event->data.key.charCode != LEFTARROW &&
  1574.       event->data.key.charCode != RIGHTARROW &&
  1575.       event->data.key.charCode != DOWNARROW &&
  1576.       event->data.key.charCode != UPARROW))) {
  1577.     return [super keyDown:event];
  1578.     }
  1579.  
  1580.     if (event->data.key.charSet == NX_ASCIISET) return [self delete:self];
  1581.  
  1582.     p.x = p.y = 0.0;
  1583.     delta = KeyMotionDeltaDefault;
  1584.     delta = floor(delta / GRID) * GRID;
  1585.     delta = MAX(delta, GRID);
  1586.  
  1587.     first = YES;
  1588.     do {
  1589.     gotOne = NO;
  1590.     if (eptr->data.key.charSet == NX_SYMBOLSET) {
  1591.         switch (eptr->data.key.charCode) {
  1592.         case LEFTARROW:
  1593.             p.x -= delta;
  1594.             gotOne = YES;
  1595.             break;
  1596.         case RIGHTARROW:
  1597.             p.x += delta;
  1598.             gotOne = YES;
  1599.             break;
  1600.         case UPARROW:
  1601.             p.y += delta;
  1602.             gotOne = YES;
  1603.             break;
  1604.         case DOWNARROW:
  1605.             p.y -= delta;
  1606.             gotOne = YES;
  1607.             break;
  1608.         default:
  1609.             break;
  1610.         }
  1611.     }
  1612.     if (eptr && gotOne && !first) [NXApp getNextEvent:NX_KEYDOWNMASK];
  1613.     first = NO;
  1614.     } while (gotOne && (eptr = [NXApp peekNextEvent:NX_KEYDOWNMASK into:&e]));
  1615.  
  1616.     if (p.x || p.y) {
  1617.     [self graphicsPerform:@selector(moveBy:) with:(id)&p andDraw:YES];
  1618.     [[self window] flushWindow];
  1619.     NXPing();
  1620.     }
  1621.  
  1622.     return self;
  1623. }
  1624.  
  1625.  
  1626. - copySelectionAsPS:(NXStream *)stream
  1627. {
  1628.     id savedglist;
  1629.  
  1630.     if (stream && [slist count]) {
  1631.     savedglist = glist;
  1632.     glist = slist;
  1633.     [self writePSToStream:stream];
  1634.     glist = savedglist;
  1635.     } else {
  1636.     return nil;
  1637.     }
  1638.  
  1639.     return self;
  1640. }
  1641.  
  1642. - copySelectionAsTIFF:(NXStream *)stream
  1643. {
  1644.     id savedglist;
  1645.  
  1646.     if (stream && [slist count]) {
  1647.     savedglist = glist;
  1648.     glist = slist;
  1649.     [self writeTIFFToStream:stream];
  1650.     glist = savedglist;
  1651.     } else {
  1652.     return nil;
  1653.     }
  1654.  
  1655.     return self;
  1656. }
  1657.  
  1658. - copySelectionToStream:(NXStream *)stream
  1659. {
  1660.     NXTypedStream *ts;
  1661.  
  1662.     if ([slist count]) {
  1663.     ts = NXOpenTypedStream(stream, NX_WRITEONLY);
  1664.     NXWriteRootObject(ts, slist);
  1665.     NXCloseTypedStream(ts);
  1666.     } else {
  1667.     return nil;
  1668.     }
  1669.  
  1670.     return self;
  1671. }
  1672.  
  1673. /*
  1674.  * Target/Action methods.
  1675.  */
  1676.  
  1677. /* Copying the selection to a stream. */
  1678. /*
  1679.  * These two method set and get the factory object used to create new
  1680.  * Graphic objects (i.e. the subclass of Graphic to use).
  1681.  * They are kind of weird since they check to see if the
  1682.  * Application object knows what the current graphic is.  If it does, then
  1683.  * it lets it handle these methods.  Otherwise, it determines the
  1684.  * current graphic by querying the sender to find out what its title is
  1685.  * and converts that title to the name to a factory object.  This allows
  1686.  * the GraphicView to stand on its own, but also use an application wide
  1687.  * tool palette if available.
  1688.  * If the GraphicView handles the current graphic by itself, it does so
  1689.  * by querying the sender of setCurrentGraphic: to find out its title.
  1690.  * It assumes, then, that that title is the name of the factory object to
  1691.  * use and calls objc_getClass() to get a pointer to it.
  1692.  * If the application is not control what our current graphic is, then
  1693.  * we restrict creations to be made only when the control key is down.
  1694.  * Otherwise, it is the other way around (control key leaves creation
  1695.  * mode).  This is due to the fact that the application can be smart
  1696.  * enough to set appropriate cursors when a tool is on.  The GraphicView
  1697.  * can't be.
  1698.  */
  1699.  
  1700. - currentGraphic
  1701. {
  1702.     if ([NXApp respondsTo:@selector(currentGraphic)]) {
  1703.     return [NXApp currentGraphic];
  1704.     } else {
  1705.     return currentGraphic;
  1706.     }
  1707. }
  1708.  
  1709. - setCurrentGraphic:sender
  1710. {
  1711.     currentGraphic = objc_getClass([[sender selectedCell] title]);
  1712.     return self;
  1713. }
  1714.  
  1715. /* Pasteboard-related target/action methods */
  1716.  
  1717. - delete:sender
  1718. {
  1719.     int i;
  1720.  
  1721.     i = [slist count];
  1722.     if (i > 0) {
  1723.     [self graphicsPerform:@selector(deactivate) andDraw:YES];
  1724.     [self graphicsPerform:@selector(activate) andDraw:NO];
  1725.     DIRTY(YES);
  1726.     while (i--) [glist removeObject:[slist objectAt:i]];
  1727.     if (originalPaste == [slist objectAt:0]) {
  1728.         [slist removeObjectAt:0];
  1729.         gvFlags.freeOriginalPaste = YES;
  1730.     }
  1731.     [slist freeObjects];
  1732.     [slist free];
  1733.     slist = [[List allocFromZone:[self zone]] init];
  1734.     gvFlags.groupInSlist = NO;
  1735.     [window flushWindow];
  1736.     }
  1737.  
  1738.     return self;
  1739. }
  1740.  
  1741. - cut:sender
  1742. /*
  1743.  * Calls copy: then delete:.
  1744.  */
  1745. {
  1746.     if ([slist count] > 0) {
  1747.     [self copy:sender];
  1748.     [self delete:sender];
  1749.     consecutivePastes = 0;
  1750.     return self;
  1751.     } else {
  1752.     return nil;
  1753.     }
  1754.  
  1755. }
  1756.  
  1757. - copy:sender
  1758. {
  1759.     if ([slist count]) {
  1760.     [self copyToPasteboard:[Pasteboard new]];
  1761.     lastPastedChangeCount = [[Pasteboard new] changeCount];
  1762.     consecutivePastes = 1;
  1763.     if (gvFlags.freeOriginalPaste) [originalPaste free];
  1764.     gvFlags.freeOriginalPaste = NO;
  1765.     originalPaste = [slist objectAt:0];
  1766.     }
  1767.     return self;
  1768. }
  1769.  
  1770. static BOOL includesType(NXAtom *types, NXAtom type)
  1771. {
  1772.     while (types && *types) {
  1773.     if (type == *types) return YES;
  1774.     types++;
  1775.     }
  1776.     return NO;
  1777. }
  1778.  
  1779. - copyToPasteboard:pboard types:(NXAtom *)typesList
  1780. /*
  1781.  * Puts all the objects in the slist into the Pasteboard by archiving
  1782.  * the slist itself.  Also registers the PostScript and TIFF types since
  1783.  * the GraphicView knows how to convert its internal type to PostScript
  1784.  * or TIFF via the copy{PS,TIFF}ToPasteboardFrom: methods.
  1785.  */
  1786. {
  1787.     char *data;
  1788.     NXStream *stream;   
  1789.     const char *types[3];
  1790.     int i = 0, length, maxlen;
  1791.  
  1792.     if ([slist count]) {
  1793.     types[i++] = DrawPboardType;
  1794.     if (!typesList || includesType(typesList, NXPostScriptPboardType)) {
  1795.         types[i++] = NXPostScriptPboardType;
  1796.     }
  1797.     if (!typesList || includesType(typesList, NXTIFFPboardType)) {
  1798.         types[i++] = NXTIFFPboardType;
  1799.     }
  1800.     stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
  1801.     [self copySelectionToStream:stream];
  1802.     NXGetMemoryBuffer(stream, &data, &length, &maxlen);
  1803.     [pboard declareTypes:types num:i owner:[self class]];
  1804.     [pboard writeType:DrawPboardType data:data length:length];
  1805.     NXCloseMemory(stream, NX_FREEBUFFER);
  1806.     return self;
  1807.     } else {
  1808.     return nil;
  1809.     }
  1810. }
  1811.  
  1812. - copyToPasteboard:pboard
  1813. {
  1814.     return [self copyToPasteboard:pboard types:NULL];
  1815. }
  1816.  
  1817. - paste:sender
  1818. /*
  1819.  * Pastes from the normal pasteboard.
  1820.  * This paste implements "smart paste" which goes like this: if the user
  1821.  * pastes in a single item (a Group is considered a single item), then
  1822.  * pastes that item again and moves that second item somewhere, then
  1823.  * subsequent pastes will be positioned at the same offset between the
  1824.  * first and second pastes (this is also known as "transform again").
  1825.  */
  1826. {
  1827.     id g, pblist;
  1828.     NXPoint offset;
  1829.     NXAtom type;
  1830.     NXRect originalBounds, secondBounds;
  1831.     static id secondPaste;
  1832.     static NXPoint pasteOffset;
  1833.  
  1834.     if (!(pblist = [self pasteFromPasteboard:[Pasteboard new]])) return nil;
  1835.  
  1836.     g = [pblist objectAt:0];
  1837.  
  1838.     if ([g isKindOf:[Image class]]) {
  1839.     [pblist free];
  1840.     return self;
  1841.     }
  1842.  
  1843.     type = drawPasteType([[Pasteboard new] types]);
  1844.     if (type == DrawPboardType) {
  1845.     if (lastPastedChangeCount != [[Pasteboard new] changeCount]) {
  1846.         consecutivePastes = 0;
  1847.         lastPastedChangeCount = [[Pasteboard new] changeCount];
  1848.         if (gvFlags.freeOriginalPaste) [originalPaste free];
  1849.         gvFlags.freeOriginalPaste = NO;
  1850.         originalPaste = g;
  1851.     } else {
  1852.         if (consecutivePastes == 1) {    /* smart paste */
  1853.         pasteOffset.x = 10.0;
  1854.         pasteOffset.y = -10.0;
  1855.         secondPaste = g;
  1856.         } else if ((consecutivePastes==2) && [pblist count]==1) {
  1857.         [originalPaste getBounds:&originalBounds];
  1858.         [secondPaste getBounds:&secondBounds];
  1859.         pasteOffset.x = secondBounds.origin.x - originalBounds.origin.x;
  1860.         pasteOffset.y = secondBounds.origin.y - originalBounds.origin.y;
  1861.         }
  1862.         offset.x = pasteOffset.x * consecutivePastes;
  1863.         offset.y = pasteOffset.y * consecutivePastes;
  1864.         [self graphicsPerform:@selector(moveBy:) with:&offset andDraw:NO];
  1865.     }
  1866.     consecutivePastes++;
  1867.     [self recacheSelection];
  1868.     }
  1869.  
  1870.     [pblist free];
  1871.  
  1872.     return self;
  1873. }
  1874.  
  1875. - pasteFromPasteboard:pboard
  1876. /*
  1877.  * Pastes any type available from the specified Pasteboard into the GraphicView.
  1878.  * If the type in the Pasteboard is the internal type, then the objects
  1879.  * are simply added to the slist and glist.  If it is PostScript or TIFF,
  1880.  * then an Image object is created using the contents of
  1881.  * the Pasteboard.  Returns a list of the pasted objects (which should be freed
  1882.  * by the caller).
  1883.  */
  1884. {
  1885.     char *data;
  1886.     int i, length;
  1887.     NXAtom type;
  1888.     NXStream *stream;
  1889.     NXTypedStream *ts;
  1890.     id g = nil, pblist = nil;
  1891.  
  1892.     type = drawPasteType([pboard types]);
  1893.     if (type) {
  1894.     [self deselectAll:self];
  1895.     [pboard readType:type data:&data length:&length];
  1896.     stream = NXOpenMemory(data, length, NX_READONLY);
  1897.     if (type == DrawPboardType) {
  1898.         ts = NXOpenTypedStream(stream, NX_READONLY);
  1899.         pblist = NXReadObject(ts);
  1900.         i = [pblist count];
  1901.         if (i) {
  1902.         DIRTY(YES);
  1903.         [self deselectAll:self];
  1904.         while (i--) {
  1905.             g = [pblist objectAt:i];
  1906.             [slist insertObject:g at:0];
  1907.             [glist insertObject:g at:0];
  1908.             gvFlags.groupInSlist = gvFlags.groupInSlist || [g isKindOf:[Group class]];
  1909.         }
  1910.         } else {
  1911.         [pblist free];
  1912.         pblist = nil;
  1913.         }
  1914.         NXCloseTypedStream(ts);
  1915.         NXCloseMemory(stream, NX_FREEBUFFER);
  1916.     } else {
  1917.         if (type == NXPostScriptPboardType || type == NXTIFFPboardType) {
  1918.         g = [self loadImageFromStream:stream at:NULL allowAlpha:(type == NXTIFFPboardType)];
  1919.         if (g) {
  1920.             pblist = [[List allocFromZone:[self zone]] initCount:1];
  1921.             [pblist addObject:g];
  1922.         }
  1923.         NXCloseMemory(stream, NX_SAVEBUFFER);
  1924.         } else {
  1925.         NXCloseMemory(stream, NX_FREEBUFFER);    // shouldn't happen!
  1926.         }
  1927.     }
  1928.     }
  1929.  
  1930.     return pblist;
  1931. }
  1932.  
  1933. /* Other target/action methods */
  1934.  
  1935. - selectAll:sender
  1936. /*
  1937.  * Selects all the items in the glist.
  1938.  */
  1939. {
  1940.     id g;
  1941.     int i;
  1942.     NXRect visibleRect;
  1943.  
  1944.     i = [glist count];
  1945.     if (!i) return self;
  1946.  
  1947.     DIRTY(YES);
  1948.     [slist free];
  1949.     slist = [[List allocFromZone:[self zone]] init];
  1950.     [[cacheWindow contentView] lockFocus];
  1951.     while (i--) {
  1952.     g = [glist objectAt:i];
  1953.     if (![g isLocked]) {
  1954.         [g select];
  1955.         [g draw:NULL];
  1956.         [slist insertObject:g at:0];
  1957.         gvFlags.groupInSlist = gvFlags.groupInSlist || [g isKindOf:[Group class]];
  1958.     }
  1959.     }
  1960.     [Graphic showFastRectFills];
  1961.     [[cacheWindow contentView] unlockFocus];
  1962.     [self getVisibleRect:&visibleRect];
  1963.     if ([self canDraw]) {
  1964.     [self lockFocus];
  1965.     [self drawSelf:&visibleRect :1];
  1966.     [self unlockFocus];
  1967.     }
  1968.     if (sender != self) [window flushWindow];
  1969.     
  1970.     return self;
  1971. }
  1972.  
  1973. - deselectAll:sender
  1974. /*
  1975.  * Deselects all the items in the slist.
  1976.  */
  1977. {
  1978.     NXRect sbounds;
  1979.  
  1980.     if ([slist count] > 0) {
  1981.     DIRTY(YES);
  1982.     [self getBBox:&sbounds of:slist];
  1983.     [self graphicsPerform:@selector(deselect) andDraw:NO];
  1984.     [self cache:&sbounds];
  1985.     [slist free];
  1986.     slist = [[List allocFromZone:[self zone]] init];
  1987.     gvFlags.groupInSlist = NO;
  1988.     if (sender != self) [window flushWindow];
  1989.     }
  1990.  
  1991.     return self;
  1992. }
  1993.  
  1994. - lock:sender
  1995. /*
  1996.  * Locks all the items in the selection so that they can't be selected
  1997.  * or resized or moved.  Useful if there are some Graphics which are getting
  1998.  * in your way.  Undo this with unlock:.
  1999.  */
  2000. {
  2001.     gvFlags.locked = ([slist count] > 0);
  2002.     if (gvFlags.locked) {
  2003.     [slist makeObjectsPerform:@selector(lock)];
  2004.     [self deselectAll:sender];
  2005.     }
  2006.     return self;
  2007. }
  2008.  
  2009. - unlock:sender
  2010. {
  2011.     [glist makeObjectsPerform:@selector(unlock)];
  2012.     gvFlags.locked = NO;
  2013.     return self;
  2014. }
  2015.  
  2016. - bringToFront:sender
  2017. /*
  2018.  * Brings each of the items in the slist to the front of the glist.
  2019.  * The item in the front of the slist will be the new front element
  2020.  * in the glist.
  2021.  */
  2022. {
  2023.     int i;
  2024.  
  2025.     i = [slist count];
  2026.     if (i) {
  2027.     DIRTY(YES);
  2028.     while (i--) [glist insertObject:[glist removeObject:[slist objectAt:i]] at:0];
  2029.     [self recacheSelection];
  2030.     }
  2031.  
  2032.     return self;
  2033. }
  2034.  
  2035. - sendToBack:sender
  2036. {
  2037.     int i, count;
  2038.  
  2039.     count = [slist count];
  2040.     if (count > 0) {
  2041.     DIRTY(YES);
  2042.     for (i = 0; i < count; i++) [glist addObject:[glist removeObject:[slist objectAt:i]]];
  2043.     [self recacheSelection];
  2044.     }
  2045.  
  2046.     return self;
  2047. }
  2048.  
  2049. - group:sender
  2050. /*
  2051.  * Creates a new Group object with the current slist as its member list.
  2052.  * See the Group class for more info.
  2053.  */
  2054. {
  2055.     id g;
  2056.     int i;
  2057.     NXRect eb;
  2058.  
  2059.     i = [slist count];
  2060.     if (i > 1) {
  2061.     DIRTY(YES);
  2062.     while (i--) [glist removeObject:[slist objectAt:i]];
  2063.     g = [[Group allocFromZone:[self zone]] initList:slist];
  2064.     [glist insertObject:g at:0];
  2065.     slist = [[List allocFromZone:[self zone]] init];
  2066.     [slist addObject:g];
  2067.     gvFlags.groupInSlist = YES;
  2068.     [self cache:[g getExtendedBounds:&eb]];
  2069.     if (sender != self) [window flushWindow];
  2070.     }
  2071.  
  2072.     return self;
  2073. }
  2074.  
  2075.  
  2076. - ungroup:sender
  2077. /*
  2078.  * Goes through the slist and ungroups any Group objects in it.
  2079.  * Does not descend any further than that (i.e. all the Group objects
  2080.  * in the slist are ungrouped, but any Group objects in those ungrouped
  2081.  * objects are NOT ungrouped).
  2082.  */
  2083. {
  2084.     id g;
  2085.     int i, k;
  2086.     NXRect sbounds;
  2087.     BOOL found = NO;
  2088.  
  2089.     [self getBBox:&sbounds of:slist];
  2090.     i = [slist count];
  2091.     while (i--) {
  2092.     g = [slist objectAt:i];
  2093.     if ([g isKindOf:[Group class]]) {
  2094.         k = [glist indexOf:g];
  2095.         [glist removeObjectAt:k];
  2096.         found = YES;
  2097.         [[g transferSubGraphicsTo:glist at:k] free];
  2098.     }
  2099.     }
  2100.  
  2101.     if (found) {
  2102.     DIRTY(YES);
  2103.     [self cache:&sbounds];
  2104.     if (sender != self) [window flushWindow];
  2105.     [self getSelection];
  2106.     }
  2107.  
  2108.     return self;
  2109. }
  2110.  
  2111. - alignLeftEdges:sender
  2112. {
  2113.     int i;
  2114.     NXRect rect;
  2115.     NXCoord minEdge = 10000.0;
  2116.  
  2117.     for (i = [slist count]-1; i >= 0; i--) {
  2118.     [[slist objectAt:i] getBounds:&rect];
  2119.     if (rect.origin.x < minEdge) minEdge = rect.origin.x;
  2120.     }
  2121.     [self graphicsPerform:@selector(moveLeftEdgeTo:) with:&minEdge andDraw:YES];
  2122.     [window flushWindow];
  2123.  
  2124.     return self;
  2125. }
  2126.  
  2127. - alignRightEdges:sender
  2128. {
  2129.     int i;
  2130.     NXRect rect;
  2131.     NXCoord maxEdge = 0.0;
  2132.  
  2133.     for (i = [slist count]-1; i >= 0; i--) {
  2134.     [[slist objectAt:i] getBounds:&rect];
  2135.     if (rect.origin.x + rect.size.width > maxEdge) maxEdge = rect.origin.x + rect.size.width;
  2136.     }
  2137.     [self graphicsPerform:@selector(moveRightEdgeTo:) with:&maxEdge andDraw:YES];
  2138.     [window flushWindow];
  2139.  
  2140.     return self;
  2141. }
  2142.  
  2143. - alignBottomEdges:sender
  2144. {
  2145.     int i;
  2146.     NXRect rect;
  2147.     NXCoord minEdge = 10000.0;
  2148.  
  2149.     for (i = [slist count]-1; i >= 0; i--) {
  2150.     [[slist objectAt:i] getBounds:&rect];
  2151.     if (rect.origin.y < minEdge) minEdge = rect.origin.y;
  2152.     }
  2153.     [self graphicsPerform:@selector(moveBottomEdgeTo:) with:&minEdge andDraw:YES];
  2154.     [window flushWindow];
  2155.  
  2156.     return self;
  2157. }
  2158.  
  2159. - alignTopEdges:sender
  2160. {
  2161.     int i;
  2162.     NXRect rect;
  2163.     NXCoord maxEdge = 0.0;
  2164.  
  2165.     for (i = [slist count]-1; i >= 0; i--) {
  2166.     [[slist objectAt:i] getBounds:&rect];
  2167.     if (rect.origin.y + rect.size.height > maxEdge) maxEdge = rect.origin.y + rect.size.height;
  2168.     }
  2169.     [self graphicsPerform:@selector(moveTopEdgeTo:) with:&maxEdge andDraw:YES];
  2170.     [window flushWindow];
  2171.  
  2172.     return self;
  2173. }
  2174.  
  2175. - alignVerticalCenters:sender
  2176. {
  2177.     int i;
  2178.     NXRect rect;
  2179.     NXCoord minEdge = 10000.0;
  2180.  
  2181.     for (i = [slist count]-1; i >= 0; i--) {
  2182.     [[slist objectAt:i] getBounds:&rect];
  2183.     if (rect.origin.x + floor(rect.size.width / 2.0) < minEdge) {
  2184.         minEdge = rect.origin.x + floor(rect.size.width / 2.0);
  2185.     }
  2186.     }
  2187.     [self graphicsPerform:@selector(moveHorizontalCenterTo:) with:&minEdge andDraw:YES];
  2188.     [window flushWindow];
  2189.  
  2190.     return self;
  2191. }
  2192.  
  2193. - alignHorizontalCenters:sender
  2194. {
  2195.     int i;
  2196.     NXRect rect;
  2197.     NXCoord minEdge = 10000.0;
  2198.  
  2199.     for (i = [slist count]-1; i >= 0; i--) {
  2200.     [[slist objectAt:i] getBounds:&rect];
  2201.     if (rect.origin.y + floor(rect.size.height / 2.0) < minEdge) {
  2202.         minEdge = rect.origin.y + floor(rect.size.height / 2.0);
  2203.     }
  2204.     }
  2205.     [self graphicsPerform:@selector(moveVerticalCenterTo:) with:&minEdge andDraw:YES];
  2206.     [window flushWindow];
  2207.  
  2208.     return self;
  2209. }
  2210.   
  2211. - changeAspectRatio:sender
  2212. {
  2213.     [self graphicsPerform:@selector(sizeToNaturalAspectRatio) andDraw:YES];
  2214.     [window flushWindow];
  2215.     return self;
  2216. }
  2217.  
  2218. - alignToGrid:sender
  2219. {
  2220.     [self graphicsPerform:@selector(alignToGrid:) with:self andDraw:YES];
  2221.     [window flushWindow];
  2222.     return self;
  2223. }
  2224.  
  2225. - sizeToGrid:sender
  2226. {
  2227.     [self graphicsPerform:@selector(sizeToGrid:) with:self andDraw:YES];
  2228.     [window flushWindow];
  2229.     return self;
  2230. }
  2231.  
  2232. - enableGrid:sender
  2233. /*
  2234.  * If the tag of the sender is non-zero, then gridding is enabled.
  2235.  * If the tag is zero, then gridding is disabled.
  2236.  */
  2237. {
  2238.     [self setGridEnabled:[sender selectedTag] ? YES : NO];
  2239.     return self;
  2240. }
  2241.  
  2242. - hideGrid:sender
  2243. /*
  2244.  * If the tag of the sender is non-zero, then the grid is made visible
  2245.  * otherwise, it is hidden (but still conceivable in effect).
  2246.  */
  2247. {
  2248.     [self setGridVisible:[sender selectedTag] ? YES : NO];
  2249.     return self;
  2250. }
  2251.  
  2252. - print:sender
  2253. {
  2254.     return [self printPSCode:sender];
  2255. }
  2256.  
  2257. /*
  2258.  * Target/Action methods to change Graphic parameters from a Control.
  2259.  * If the sender is a Matrix, then the selectedRow is used to determine
  2260.  * the value to use (for linecap, linearrow, etc.) otherwise, the
  2261.  * sender's floatValue or intValue is used (whichever is appropriate).
  2262.  * This allows interface builders the flexibility to design different
  2263.  * ways of setting those values.
  2264.  */
  2265.  
  2266. - takeGridValueFrom:sender
  2267. {
  2268.     [self setGridSpacing:[sender intValue]];
  2269.     return self;
  2270. }
  2271.  
  2272. - takeGridGrayFrom:sender
  2273. {
  2274.     [self setGridGray:[sender floatValue]];
  2275.     return self;
  2276. }
  2277.  
  2278. - takeGrayValueFrom:sender
  2279. {
  2280.     float value;
  2281.  
  2282.     value = [sender floatValue];
  2283.     [self graphicsPerform:@selector(setGray:) with:&value andDraw:YES];
  2284.     [window flushWindow];
  2285.  
  2286.     return self;
  2287. }
  2288.  
  2289. - takeLineWidthFrom:sender
  2290. {
  2291.     float value;
  2292.  
  2293.     value = [sender floatValue];
  2294.     [self graphicsPerform:@selector(setLineWidth:) with:&value andDraw:YES];
  2295.     [window flushWindow];
  2296.  
  2297.     return self;
  2298. }
  2299.  
  2300. - takeLineJoinFrom:sender
  2301. {
  2302.     if ([sender respondsTo:@selector(selectedRow)]) {
  2303.     [self graphicsPerform:@selector(setLineJoin:) with:(void *)[sender selectedRow] andDraw:YES];
  2304.     } else {
  2305.     [self graphicsPerform:@selector(setLineJoin:) with:(void *)[sender intValue] andDraw:YES];
  2306.     }
  2307.     [window flushWindow];
  2308.     return self;
  2309. }
  2310.  
  2311. - takeLineCapFrom:sender
  2312. {
  2313.     if ([sender respondsTo:@selector(selectedRow)]) {
  2314.     [self graphicsPerform:@selector(setLineCap:) with:(void *)[sender selectedRow] andDraw:YES];
  2315.     } else {
  2316.     [self graphicsPerform:@selector(setLineCap:) with:(void *)[sender intValue] andDraw:YES];
  2317.     }
  2318.     [window flushWindow];
  2319.     return self;
  2320. }
  2321.  
  2322. - takeLineArrowFrom:sender
  2323. {
  2324.     if ([sender respondsTo:@selector(selectedRow)]) {
  2325.     [self graphicsPerform:@selector(setLineArrow:) with:(void *)[sender selectedRow] andDraw:YES];
  2326.     } else {
  2327.     [self graphicsPerform:@selector(setLineArrow:) with:(void *)[sender intValue] andDraw:YES];
  2328.     }
  2329.     [window flushWindow];
  2330.     return self;
  2331. }
  2332.  
  2333. - takeFillValueFrom:sender
  2334. {
  2335.     if ([sender respondsTo:@selector(selectedRow)]) {
  2336.     [self graphicsPerform:@selector(setFill:) with:(void *)[sender selectedRow] andDraw:YES];
  2337.     } else {
  2338.     [self graphicsPerform:@selector(setFill:) with:(void *)[sender intValue] andDraw:YES];
  2339.     }
  2340.     [window flushWindow];
  2341.     return self;
  2342. }
  2343.  
  2344. - takeFrameValueFrom:sender
  2345. {
  2346.     if ([sender respondsTo:@selector(selectedRow)]) {
  2347.     [self graphicsPerform:@selector(setFramed:) with:(void *)[sender selectedRow] andDraw:YES];
  2348.     } else {
  2349.     [self graphicsPerform:@selector(setFramed:) with:(void *)[sender intValue] andDraw:YES];
  2350.     }
  2351.     [window flushWindow];
  2352.     return self;
  2353. }
  2354.  
  2355. - takeLineColorFrom:sender
  2356. {
  2357.     NXColor color = [sender color];
  2358.     [self graphicsPerform:@selector(setLineColor:) with:(void *)&color andDraw:YES];
  2359.     [window flushWindow];
  2360.     return self;
  2361. }
  2362.  
  2363. - takeFillColorFrom:sender
  2364. {
  2365.     NXColor color = [sender color];
  2366.     [self graphicsPerform:@selector(setFillColor:) with:(void *)&color andDraw:YES];
  2367.     [window flushWindow];
  2368.     return self;
  2369. }
  2370.  
  2371. - takeTextColorFrom:sender
  2372. {
  2373.     NXColor color = [sender color];
  2374.     [self graphicsPerform:@selector(setGraphicTextColor:) with:(void *)&color andDraw:YES];
  2375.     [window flushWindow];
  2376.     return self;
  2377. }
  2378.  
  2379. - changeFont:sender
  2380. {
  2381.     if ([window firstResponder] == self) {
  2382.     [self graphicsPerform:@selector(changeFont:) with:sender andDraw:YES];
  2383.     [window flushWindow];
  2384.     }
  2385.     return self;
  2386. }
  2387.  
  2388. /* Accepting becoming the First Responder */
  2389.  
  2390. - (BOOL)acceptsFirstResponder
  2391. /*
  2392.  * GraphicView always wants to become the first responder when it is
  2393.  * clicked on in a window, so it returns YES from this method.
  2394.  */
  2395. {
  2396.     return YES;
  2397. }
  2398.  
  2399. /* Printing */
  2400.  
  2401. - beginPrologueBBox:(NXRect *)boundingBox creationDate:(char *)dateCreated
  2402.     createdBy:(char *)anApplication fonts:(char *)fontNames
  2403.     forWhom:(char *)user pages:(int )numPages title:(char *)aTitle
  2404. /*
  2405.  * Include the window title as the name of the document when printing.
  2406.  */
  2407. {
  2408.     char *s;
  2409.     char name[MAXPATHLEN+1];
  2410.     const char *title = [window title];
  2411.  
  2412.     strcpy(name, title);
  2413.     s = strchr(name, ' ');
  2414.     if (s) *s = '\0';
  2415.     return [super beginPrologueBBox:boundingBox creationDate:dateCreated
  2416.         createdBy:anApplication fonts:fontNames
  2417.         forWhom:user pages:numPages title:name];
  2418. }
  2419.  
  2420. - beginSetup
  2421. /*
  2422.  * Spit out the custom PostScript defs.
  2423.  */
  2424. {
  2425.     [super beginSetup];
  2426.     PSInit();
  2427.     return self;
  2428. }
  2429.  
  2430. /* Archiver-related methods. */
  2431.  
  2432. - awake
  2433. /*
  2434.  * After the GraphicView is unarchived, its cache must be created.
  2435.  * If we are loading in this GraphicView just to print it, then we need
  2436.  * not load up our cache.
  2437.  */
  2438. {
  2439.     if (!InMsgPrint) {
  2440.     cacheWindow = createCache(&bounds.size, [self zone]);
  2441.     [self cache:&bounds];
  2442.     }
  2443.     initClassVars();
  2444.     return [super awake];
  2445. }
  2446.  
  2447. - write:(NXTypedStream *)stream
  2448. /*
  2449.  * Writes out the glist and the flags.
  2450.  * No need to write out the slist since it can be regenerated from the glist.
  2451.  */
  2452. {
  2453.     [super write:stream];
  2454.     gvFlags.dirty = NO;
  2455.     [window setDocEdited:NO];
  2456.     NXWriteTypes(stream, "@sf", &glist, &gvFlags, &gridGray);
  2457.     NXWriteObject(stream, editView);
  2458.     return self;
  2459. }
  2460.  
  2461. - read:(NXTypedStream *)stream
  2462. /*
  2463.  * Reads in the glist and the flags, and regenerates the slist from the glist.
  2464.  */
  2465. {
  2466.     int i;
  2467.     id g, newg;
  2468.  
  2469.     [super read:stream];
  2470.     NXReadTypes(stream, "@sf", &glist, &gvFlags, &gridGray);
  2471.     for (i = [glist count]-1; i >= 0; i--) {
  2472.     g = [glist objectAt:i];
  2473.     newg = [g replaceWithImage];
  2474.     if (g != newg) {
  2475.         if (g) {
  2476.         [glist replaceObjectAt:i with:newg];
  2477.         } else {
  2478.         [glist removeObjectAt:i];
  2479.         }
  2480.     }
  2481.     }
  2482.     [self getSelection];
  2483.     [self resetGUP];
  2484.     if (NXTypedStreamClassVersion(stream, [self name]) < 1) {
  2485.     editView = createEditView(self);
  2486.     } else {
  2487.     editView = NXReadObject(stream);
  2488.     }
  2489.  
  2490.     return self;
  2491. }
  2492.  
  2493. /* Methods to deal with being/becoming the First Responder */
  2494.  
  2495. /* Validates whether a menu command makes sense now */
  2496.  
  2497. - (BOOL)validateCommand:menuCell
  2498. /*
  2499.  * Can be called to see if the specified action is valid on this view now.
  2500.  * It returns NO if the GraphicView knows that action is not valid now,
  2501.  * otherwise it returns YES.  Note the use of the Pasteboard change
  2502.  * count so that the GraphicView does not have to look into the Pasteboard
  2503.  * every time paste: is validated.
  2504.  */
  2505. {
  2506.     id pb;
  2507.     int i, count, gcount;
  2508.     SEL action = [menuCell action];
  2509.     static BOOL pboardHasPasteableType = NO;
  2510.     static int cachedPasteboardChangeCount = 0;
  2511.  
  2512.     if (action == @selector(bringToFront:)) {
  2513.     if ((count = [slist count]) && [glist count] > count) {
  2514.         for (i = 0; i < count; i++) {
  2515.         if ([slist objectAt:i] != [glist objectAt:i]) {
  2516.             return YES;
  2517.         }
  2518.         }
  2519.     }
  2520.     return NO;
  2521.     } else if (action == @selector(sendToBack:)) {
  2522.     if ((count = [slist count]) && (gcount = [glist count]) > count) {
  2523.         for (i = 1; i <= count; i++) {
  2524.         if ([slist objectAt:count-i] != [glist objectAt:gcount-i]) {
  2525.             return YES;
  2526.         }
  2527.         }
  2528.     }
  2529.     return NO;
  2530.     } else if (action == @selector(group:) ||
  2531.     action == @selector(alignLeftEdges:) ||
  2532.     action == @selector(alignRightEdges:) ||
  2533.     action == @selector(alignBottomEdges:) ||
  2534.     action == @selector(alignTopEdges:) ||
  2535.     action == @selector(alignVerticalCenters:) ||
  2536.     action == @selector(alignHorizontalCenters:)) {
  2537.     return([slist count] > 1);
  2538.     } else if (action == @selector(ungroup:)) {
  2539.     return(gvFlags.groupInSlist && [slist count] > 0);
  2540.     } else if (action == @selector(deselectAll:) ||
  2541.     action == @selector(lock:) ||
  2542.     action == @selector(changeAspectRatio:) ||
  2543.     action == @selector(cut:) ||
  2544.     action == @selector(copy:)) {
  2545.     return([slist count] > 0);
  2546.     } else if (action == @selector(alignToGrid:) ||
  2547.     action == @selector(sizeToGrid:)) {
  2548.     return(GRID > 1 && [slist count] > 0);
  2549.     } else if (action == @selector(unlock:)) {
  2550.     return gvFlags.locked;
  2551.     } else if (action == @selector(selectAll:)) {
  2552.     return([glist count] > [slist count]);
  2553.     } else if (action == @selector(paste:)) {
  2554.     pb = [Pasteboard new];
  2555.     count = [pb changeCount];
  2556.     if (count != cachedPasteboardChangeCount) {
  2557.         cachedPasteboardChangeCount = count;
  2558.         pboardHasPasteableType = (drawPasteType([pb types]) != NULL);
  2559.     }
  2560.     return pboardHasPasteableType;
  2561.     } else if (action == @selector(hideGrid:)) {
  2562.     if (GRID >= 4) {
  2563.         if ([self gridIsVisible]) {
  2564.         if (strcmp([menuCell title], "Hide Grid")) {
  2565.             [menuCell setTitle:"Hide Grid"];
  2566.             [menuCell setTag:0];
  2567.             [menuCell setEnabled:NO];
  2568.         }
  2569.         } else {
  2570.         if (strcmp([menuCell title], "Show Grid")) {
  2571.             [menuCell setTitle:"Show Grid"];
  2572.             [menuCell setTag:1];
  2573.             [menuCell setEnabled:NO];
  2574.         }
  2575.         }
  2576.         return YES;
  2577.     } else {
  2578.         return NO;
  2579.     }
  2580.     } else if (action == @selector(enableGrid:)) {
  2581.     if (gvFlags.grid > 1) {
  2582.         if ([self gridIsEnabled]) {
  2583.         if (strcmp([menuCell title], "Turn Grid Off")) {
  2584.             [menuCell setTitle:"Turn Grid Off"];
  2585.             [menuCell setTag:0];
  2586.             [menuCell setEnabled:NO];
  2587.         }
  2588.         } else {
  2589.         if (strcmp([menuCell title], "Turn Grid On")) {
  2590.             [menuCell setTitle:"Turn Grid On"];
  2591.             [menuCell setTag:1];
  2592.             [menuCell setEnabled:NO];
  2593.         }
  2594.         }
  2595.         return YES;
  2596.     } else {
  2597.         return NO;
  2598.     }
  2599.     } else if (action == @selector(print:)) {
  2600.     return([glist count] > 0);
  2601.     }
  2602.  
  2603.     return YES;
  2604. }
  2605.  
  2606. /* Useful scrolling routines. */
  2607.  
  2608. - scrollGraphicToVisible:graphic
  2609. {
  2610.     NXPoint p;
  2611.     NXRect eb;
  2612.  
  2613.     p = bounds.origin;
  2614.     NXContainRect([graphic getExtendedBounds:&eb], &bounds);
  2615.     p.x -= bounds.origin.x;
  2616.     p.y -= bounds.origin.y;
  2617.     if (p.x || p.y) {
  2618.     [graphic moveBy:&p];
  2619.     bounds.origin.x += p.x;
  2620.     bounds.origin.y += p.y;
  2621.     [self scrollRectToVisible:[graphic getExtendedBounds:&eb]];
  2622.     }
  2623.  
  2624.     return self;
  2625. }
  2626.  
  2627. - scrollPointToVisible:(const NXPoint *)point
  2628. {
  2629.     NXRect r;
  2630.  
  2631.     r.origin.x = point->x - 5.0;
  2632.     r.origin.y = point->y - 5.0;
  2633.     r.size.width = r.size.height = 10.0;
  2634.  
  2635.     return [self scrollRectToVisible:&r];
  2636. }
  2637.  
  2638. @end
  2639.  
  2640.